SPB Git forge

spb/immbot-ai

Public
1commits 1branches 0releases
1.5 MBsize
maindefault branch
20 days agolast push
TypeScript 98.3% CSS 0.9% Shell 0.7%
2.4 KB · 52 lines typescript
Raw Blame History
1// Diapositives d'une séance (contenu structuré) — et PDF original via ?pdf=1.2import { NextResponse } from "next/server";3import { createReadStream, existsSync, statSync } from "node:fs";4import { Readable } from "node:stream";5import { resolve } from "node:path";6import { apiError, requireEnrollment } from "@/lib/api.ts";7import { AuthError, requireUser } from "@/lib/auth/session.ts";8import { all, get } from "@/lib/db/index.ts";9import { normalizeCourse } from "@/lib/learning/helpers.ts";1011export async function GET(req: Request, ctx: { params: Promise<{ course: string; week: string }> }) {12  try {13    const user = await requireUser();14    const params = await ctx.params;15    const course = normalizeCourse(params.course);16    requireEnrollment(user.id, course);17    const week = parseInt(params.week, 10);18    if (!Number.isInteger(week) || week < 1 || week > 14) throw new AuthError(404, "Séance inconnue.");1920    const doc = get<{ id: number; title: string; path: string }>(21      "SELECT id, title, path FROM documents WHERE course_code = ? AND doc_type = 'slides' AND week = ? AND visible_to_students = 1",22      course, week23    );24    if (!doc) throw new AuthError(404, "Séance introuvable.");2526    // PDF original compilé (même chemin que la source .tex)27    if (new URL(req.url).searchParams.get("pdf") === "1") {28      const pdfPath = resolve(process.cwd(), "..", doc.path.replace(/\.tex$/, ".pdf"));29      if (!existsSync(pdfPath)) return NextResponse.json({ error: "PDF non disponible pour cette séance." }, { status: 404 });30      const size = statSync(pdfPath).size;31      const stream = Readable.toWeb(createReadStream(pdfPath)) as ReadableStream;32      return new Response(stream, {33        headers: {34          "Content-Type": "application/pdf",35          "Content-Length": String(size),36          "Content-Disposition": `inline; filename="${course}-seance${String(week).padStart(2, "0")}.pdf"`,37          "Cache-Control": "private, max-age=3600",38        },39      });40    }4142    const slides = all<{ ref_number: number; title: string; section_title: string; display_content: string; box_types: string }>(43      `SELECT ref_number, title, section_title, display_content, box_types44       FROM chunks WHERE document_id = ? AND ref_type = 'slide' ORDER BY ref_number, seq`,45      doc.id46    );47    return NextResponse.json({ deck: { week, title: doc.title }, slides });48  } catch (e) {49    return apiError(e);50  }51}52